home *** CD-ROM | disk | FTP | other *** search
/ Chip 2005 August (Alt) / CHIP 2005-08.1.iso / program / code / Firefox_1.0.5.exe / browser.xpi / bin / components / nsHelperAppDlg.js < prev    next >
Encoding:
JavaScript  |  2005-07-11  |  35.5 KB  |  916 lines

  1. /*
  2. */
  3.  
  4. /* This file implements the nsIHelperAppLauncherDialog interface.
  5.  *
  6.  * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
  7.  * comprised of:
  8.  *   - a JS constructor function
  9.  *   - a prototype providing all the interface methods and implementation stuff
  10.  *
  11.  * In addition, this file implements an nsIModule object that registers the
  12.  * nsUnknownContentTypeDialog component.
  13.  */
  14.  
  15.  
  16. /* ctor
  17.  */
  18. function nsUnknownContentTypeDialog() {
  19.     // Initialize data properties.
  20.     this.mLauncher = null;
  21.     this.mContext  = null;
  22.     this.mSourcePath = null;
  23.     this.chosenApp = null;
  24.     this.givenDefaultApp = false;
  25.     this.updateSelf = true;
  26.     this.mTitle    = "";
  27. }
  28.  
  29. nsUnknownContentTypeDialog.prototype = {
  30.     nsIMIMEInfo  : Components.interfaces.nsIMIMEInfo,
  31.  
  32.     // This "class" supports nsIHelperAppLauncherDialog, and nsISupports.
  33.     QueryInterface: function (iid) {
  34.         if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
  35.             !iid.equals(Components.interfaces.nsISupports)) {
  36.             throw Components.results.NS_ERROR_NO_INTERFACE;
  37.         }
  38.         return this;
  39.     },
  40.  
  41.     // ---------- nsIHelperAppLauncherDialog methods ----------
  42.  
  43.     // show: Open XUL dialog using window watcher.  Since the dialog is not
  44.     //       modal, it needs to be a top level window and the way to open
  45.     //       one of those is via that route).
  46.     show: function(aLauncher, aContext)  {  
  47.       this.mLauncher = aLauncher;
  48.       this.mContext  = aContext;
  49.       // Display the dialog using the Window Watcher interface.
  50.       
  51.       var ir = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
  52.       var dwi = ir.getInterface(Components.interfaces.nsIDOMWindowInternal);
  53.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  54.                 .getService(Components.interfaces.nsIWindowWatcher);
  55.       this.mDialog = ww.openWindow(dwi,
  56.                                    "chrome://mozapps/content/downloads/unknownContentType.xul",
  57.                                    null,
  58.                                    "chrome,centerscreen,titlebar,dialog=yes,dependent",
  59.                                    null);
  60.       // Hook this object to the dialog.
  61.       this.mDialog.dialog = this;
  62.       
  63.       // Hook up utility functions. 
  64.       this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;
  65.       
  66.       // Watch for error notifications.
  67.       this.progressListener.helperAppDlg = this;
  68.       this.mLauncher.setWebProgressListener(this.progressListener);
  69.     },
  70.  
  71.     // promptForSaveToFile:  Display file picker dialog and return selected file.
  72.     //                       This is called by the External Helper App Service
  73.     //                       after the ucth dialog calls |saveToDisk| with a null
  74.     //                       target filename (no target, therefore user must pick).
  75.     //
  76.     //                       Alternatively, if the user has selected to have all
  77.     //                       files download to a specific location, return that
  78.     //                       location and don't ask via the dialog. 
  79.     //
  80.     // Note - this function is called without a dialog, so it cannot access any part
  81.     // of the dialog XUL as other functions on this object do. 
  82.     promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension) {
  83.       var result = "";
  84.       
  85.       this.mLauncher = aLauncher;
  86.  
  87.       // If the user is always downloading to the same location, the default download
  88.       // folder is stored in preferences. If a value is found stored, use that 
  89.       // automatically and don't ask via a dialog. 
  90.       const kDownloadFolderPref = "browser.download.defaultFolder";
  91.       var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
  92.       try {
  93.         result = prefs.getComplexValue(kDownloadFolderPref, Components.interfaces.nsILocalFile);
  94.         result = this.validateLeafName(result, aDefaultFile, aSuggestedFileExtension);
  95.       }
  96.       catch (e) { 
  97.         // If we get here, it's because we have a new profile and the user has never configured download
  98.         // options, so "browser.download.defaultFolder" is not set yet. If the default is autodownload, 
  99.         // we need to discover the default save location. 
  100.         var autodownload = prefs.getBoolPref("browser.download.useDownloadDir");
  101.         if (autodownload) {
  102.           function getSpecialFolderKey(aFolderType) 
  103.           {
  104.             return aFolderType == "Desktop" ? "DeskP" : "Pers";
  105.             return "Home";
  106.           }
  107.           
  108.           function getDownloadsFolder(aFolder)
  109.           {
  110.             var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
  111.  
  112.             var dir = fileLocator.get(getSpecialFolderKey(aFolder), Components.interfaces.nsILocalFile);
  113.             
  114.             var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);
  115.             bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
  116.  
  117.             var description = bundle.GetStringFromName("myDownloads");
  118.             if (aFolder != "Desktop")
  119.               dir.append(description);
  120.               
  121.             return dir;
  122.           }
  123.  
  124.           var defaultFolder = null;
  125.           switch (prefs.getIntPref("browser.download.folderList")) {
  126.           case 0:
  127.             defaultFolder = getDownloadsFolder("Desktop")
  128.             break;
  129.           case 1:
  130.             defaultFolder = getDownloadsFolder("Downloads");
  131.             break;
  132.           case 2:
  133.             defaultFolder = prefs.getComplexValue("browser.download.dir", Components.interfaces.nsILocalFile);
  134.             break;
  135.           }
  136.           
  137.           // While we're here, set the pref too so that we don't keep coming back into this less efficient
  138.           // code block. 
  139.           prefs.setComplexValue("browser.download.defaultFolder", Components.interfaces.nsILocalFile, defaultFolder);
  140.           
  141.           result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension);
  142.         }
  143.       }
  144.       
  145.       if (!result) {
  146.         // Use file picker to show dialog.
  147.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  148.         var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  149.  
  150.         var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);
  151.         bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
  152.  
  153.         var windowTitle = bundle.GetStringFromName("saveDialogTitle");
  154.         var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowInternal);
  155.         picker.init(parent, windowTitle, nsIFilePicker.modeSave);
  156.         picker.defaultString = aDefaultFile;
  157.  
  158.         if (aSuggestedFileExtension) {
  159.           // aSuggestedFileExtension includes the period, so strip it
  160.           picker.defaultExtension = aSuggestedFileExtension.substring(1);
  161.         } 
  162.         else {
  163.           try {
  164.             picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
  165.           } 
  166.           catch (ex) { }
  167.         }
  168.  
  169.         var wildCardExtension = "*";
  170.         if (aSuggestedFileExtension) {
  171.           wildCardExtension += aSuggestedFileExtension;
  172.           picker.appendFilter(this.mLauncher.MIMEInfo.Description, wildCardExtension);
  173.         }
  174.  
  175.         picker.appendFilters( nsIFilePicker.filterAll );
  176.  
  177.         // Pull in the user's preferences and get the default download directory.
  178.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
  179.         try {
  180.           var startDir = prefs.getComplexValue("browser.download.dir", Components.interfaces.nsILocalFile);
  181.           if (startDir.exists()) {
  182.             picker.displayDirectory = startDir;
  183.           }
  184.         } 
  185.         catch(exception) { }
  186.  
  187.         var dlgResult = picker.show();
  188.  
  189.         if (dlgResult == nsIFilePicker.returnCancel) {
  190.           // null result means user cancelled.
  191.           return null;
  192.         }
  193.  
  194.  
  195.         // Be sure to save the directory the user chose through the Save As... 
  196.         // dialog  as the new browser.download.dir
  197.         result = picker.file;
  198.  
  199.         if (result) {
  200.           var newDir = result.parent;
  201.           prefs.setComplexValue("browser.download.dir", Components.interfaces.nsILocalFile, newDir);
  202.         }
  203.       }
  204.       return result;
  205.     },
  206.     
  207.     validateLeafName: function (aLocalFile, aLeafName, aFileExt)
  208.     {
  209.       if (aLeafName == "")
  210.         aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
  211.       aLocalFile.append(aLeafName);
  212.  
  213.       this.makeFileUnique(aLocalFile);
  214.  
  215.       if (aLocalFile.isExecutable() && !this.mLauncher.targetFile.isExecutable()) {
  216.         var f = aLocalFile.clone();
  217.         aLocalFile.leafName = aLocalFile.leafName + "." + this.mLauncher.MIMEInfo.primaryExtension; 
  218.  
  219.         f.remove(false);
  220.         this.makeFileUnique(aLocalFile);
  221.       }
  222.       return aLocalFile;
  223.     },
  224.  
  225.     makeFileUnique: function (aLocalFile)
  226.     {
  227.       try {
  228.         // Since we're automatically downloading, we don't get the file picker's 
  229.         // logic to check for existing files, so we need to do that here.
  230.         //
  231.         // Note - this code is identical to that in 
  232.         //   browser/base/content/contentAreaUtils.js. 
  233.         // If you are updating this code, update that code too! We can't share code
  234.         // here since this is called in a js component. 
  235.         while (aLocalFile.exists()) {
  236.           var parts = /.+-(\d+)(\..*)?$/.exec(aLocalFile.leafName);
  237.           if (parts) {
  238.             aLocalFile.leafName = aLocalFile.leafName.replace(/((\d+)\.)|((\d+)$)/,
  239.                                                               function (str, dot, dotNum, noDot, noDotNum, pos, s) {
  240.                                                                 return (parseInt(str) + 1) + (dot ? "." : "");
  241.                                                               });
  242.           }
  243.           else {
  244.             aLocalFile.leafName = aLocalFile.leafName.replace(/\.|$/, "-1$&");
  245.           }
  246.         }
  247.         aLocalFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
  248.       }
  249.       catch (e) {
  250.         dump("*** exception in validateLeafName: " + e + "\n");
  251.         if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) {
  252.           aLocalFile.append("unnamed");
  253.           if (aLocalFile.exists())
  254.             aLocalFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
  255.         }
  256.       }
  257.     },
  258.     
  259.     // ---------- implementation methods ----------
  260.  
  261.     // Web progress listener so we can detect errors while mLauncher is
  262.     // streaming the data to a temporary file.
  263.     progressListener: {
  264.         // Implementation properties.
  265.         helperAppDlg: null,
  266.  
  267.         // nsIWebProgressListener methods.
  268.         // Look for error notifications and display alert to user.
  269.         onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
  270.             if ( aStatus != Components.results.NS_OK ) {
  271.                 // Get prompt service.
  272.                 var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  273.                                    .getService( Components.interfaces.nsIPromptService );
  274.                 // Display error alert (using text supplied by back-end).
  275.                 prompter.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
  276.  
  277.                 // Close the dialog.
  278.                 this.helperAppDlg.onCancel();
  279.                 if ( this.helperAppDlg.mDialog ) {
  280.                     this.helperAppDlg.mDialog.close();
  281.                 }
  282.             }
  283.         },
  284.  
  285.         // Ignore onProgressChange, onStateChange, onLocationChange, and onSecurityChange notifications.
  286.         onProgressChange: function( aWebProgress,
  287.                                     aRequest,
  288.                                     aCurSelfProgress,
  289.                                     aMaxSelfProgress,
  290.                                     aCurTotalProgress,
  291.                                     aMaxTotalProgress ) {
  292.         },
  293.  
  294.         onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
  295.         },
  296.  
  297.         onLocationChange: function( aWebProgress, aRequest, aLocation ) {
  298.         },
  299.  
  300.         onSecurityChange: function( aWebProgress, aRequest, state ) {
  301.         }
  302.     },
  303.  
  304.     // initDialog:  Fill various dialog fields with initial content.
  305.     initDialog : function() {
  306.       // Put file name in window title.
  307.       var win   = this.dialogElement( "unknownContentType" );
  308.       var suggestedFileName = this.mLauncher.suggestedFileName;
  309.  
  310.       // Some URIs do not implement nsIURL, so we can't just QI.
  311.       var url   = this.mLauncher.source;
  312.       var fname = "";
  313.       this.mSourcePath = url.prePath;
  314.       try {
  315.           url = url.QueryInterface( Components.interfaces.nsIURL );
  316.           // A url, use file name from it.
  317.           fname = url.fileName;
  318.           this.mSourcePath += url.directory;
  319.       } catch (ex) {
  320.           // A generic uri, use path.
  321.           fname = url.path;
  322.           this.mSourcePath += url.path;
  323.       }
  324.  
  325.       if (suggestedFileName)
  326.         fname = suggestedFileName;
  327.       
  328.       var displayName = fname.replace(/ +/g, " ");
  329.  
  330.       this.mTitle = this.dialogElement("strings").getFormattedString("title", [displayName]);
  331.       win.setAttribute( "title", this.mTitle );
  332.  
  333.       // Put content type, filename and location into intro.
  334.       this.initIntro(url, fname, displayName);
  335.  
  336.       var iconString = "moz-icon://" + fname + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
  337.       this.dialogElement("contentTypeImage").setAttribute("src", iconString);
  338.  
  339.       this.initAppAndSaveToDiskValues();
  340.  
  341.       // Initialize "always ask me" box. This should always be disabled
  342.       // and set to true for the ambiguous type application/octet-stream.
  343.       // We don't also check for application/x-msdownload here since we
  344.       // want users to be able to autodownload .exe files. 
  345.       var rememberChoice = this.dialogElement("rememberChoice");
  346.  
  347.       var mimeType = this.mLauncher.MIMEInfo.MIMEType;
  348.       if (mimeType == "application/octet-stream" || 
  349.           mimeType == "application/x-msdownload" ||
  350.           this.mLauncher.targetFile.isExecutable()) {
  351.         rememberChoice.checked = false;
  352.         rememberChoice.disabled = true;
  353.       }
  354.       else {
  355.         rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  356.       }
  357.       this.toggleRememberChoice(rememberChoice);
  358.  
  359.       // XXXben - menulist won't init properly, hack. 
  360.       var openHandler = this.dialogElement("openHandler");
  361.       openHandler.parentNode.removeChild(openHandler);
  362.       var openHandlerBox = this.dialogElement("openHandlerBox");
  363.       openHandlerBox.appendChild(openHandler);
  364.  
  365.       this.mDialog.setTimeout("dialog.postShowCallback()", 0);
  366.       
  367.       this.mDialog.document.documentElement.getButton("accept").disabled = true;
  368.       const nsITimer = Components.interfaces.nsITimer;
  369.       this._timer = Components.classes["@mozilla.org/timer;1"]
  370.                               .createInstance(nsITimer);
  371.       this._timer.initWithCallback(this, 250, nsITimer.TYPE_ONE_SHOT);
  372.     },
  373.     
  374.     _timer: null,
  375.     notify: function (aTimer) {
  376.       if (!this._blurred)
  377.         this.mDialog.document.documentElement.getButton('accept').disabled = false;
  378.       this._delayExpired = true;
  379.     },
  380.     
  381.     postShowCallback: function () {
  382.       this.mDialog.sizeToContent();
  383.  
  384.       // Set initial focus
  385.       this.dialogElement("mode").focus();
  386.     },
  387.  
  388.     // initIntro:
  389.     initIntro: function(url, filename, displayname) {
  390.         this.dialogElement( "location" ).value = displayname;
  391.         this.dialogElement( "location" ).setAttribute("realname", filename);
  392.         this.dialogElement( "location" ).setAttribute("tooltiptext", displayname);
  393.  
  394.         // if mSourcePath is a local file, then let's use the pretty path name instead of an ugly
  395.         // url...
  396.         var pathString = this.mSourcePath;
  397.         try 
  398.         {
  399.           var fileURL = url.QueryInterface(Components.interfaces.nsIFileURL);
  400.           if (fileURL)
  401.           {
  402.             var fileObject = fileURL.file;
  403.             if (fileObject)
  404.             {
  405.               var parentObject = fileObject.parent;
  406.               if (parentObject)
  407.               {
  408.                 pathString = parentObject.path;
  409.               }
  410.             }
  411.           }
  412.         } catch(ex) {}
  413.  
  414.         if (pathString == this.mSourcePath)
  415.         {
  416.           // wasn't a fileURL
  417.           var tmpurl = url.clone(); // don't want to change the real url
  418.           try {
  419.             tmpurl.userPass = "";
  420.           } catch (ex) {}
  421.           pathString = tmpurl.prePath;
  422.         }
  423.  
  424.         // Set the location text, which is separate from the intro text so it can be cropped
  425.         var location = this.dialogElement( "source" );
  426.         location.value = pathString;
  427.         location.setAttribute("tooltiptext", this.mSourcePath);
  428.         
  429.         // Show the type of file. 
  430.         var type = this.dialogElement("type");
  431.         var mimeInfo = this.mLauncher.MIMEInfo;
  432.         
  433.         // 1. Try to use the pretty description of the type, if one is available.
  434.         var typeString = mimeInfo.Description;
  435.         
  436.         if (typeString == "") {
  437.           // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
  438.           var primaryExtension = "";
  439.           try {
  440.             primaryExtension = mimeInfo.primaryExtension;
  441.           }
  442.           catch (ex) {
  443.           }
  444.           if (primaryExtension != "")
  445.             typeString = primaryExtension.toUpperCase() + " file";
  446.           // 3. If we can't even do that, just give up and show the MIME type. 
  447.           else
  448.             typeString = mimeInfo.MIMEType;
  449.         }
  450.         
  451.         type.value = typeString;
  452.     },
  453.     
  454.     _blurred: false,
  455.     _delayExpired: false, 
  456.     onBlur: function(aEvent) {
  457.       if (aEvent.target != this.mDialog.document)
  458.         return;
  459.       this._blurred = true;
  460.       this.mDialog.document.documentElement.getButton("accept").disabled = true;
  461.     },
  462.     
  463.     onFocus: function(aEvent) {
  464.       if (aEvent.target != this.mDialog.document)
  465.         return;
  466.       this._blurred = false;
  467.       if (this._delayExpired) {
  468.         var script = "document.documentElement.getButton('accept').disabled = false";
  469.         this.mDialog.setTimeout(script, 250);
  470.       }
  471.     },
  472.  
  473.     // Returns true if opening the default application makes sense.
  474.     openWithDefaultOK: function() {
  475.         var result;
  476.  
  477.         // The checking is different on Windows...
  478.         // Windows presents some special cases.
  479.         // We need to prevent use of "system default" when the file is
  480.         // executable (so the user doesn't launch nasty programs downloaded
  481.         // from the web), and, enable use of "system default" if it isn't
  482.         // executable (because we will prompt the user for the default app
  483.         // in that case).
  484.         
  485.         // Need to get temporary file and check for executable-ness.
  486.         var tmpFile = this.mLauncher.targetFile;
  487.         
  488.         //  Default is Ok if the file isn't executable (and vice-versa).
  489.         return !tmpFile.isExecutable();
  490.     },
  491.     
  492.     // Set "default" application description field.
  493.     initDefaultApp: function() {
  494.       // Use description, if we can get one.
  495.       var desc = this.mLauncher.MIMEInfo.defaultDescription;
  496.       if (desc) {
  497.         var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]);
  498.         this.dialogElement("defaultHandler").label = defaultApp;
  499.       }
  500.       else {
  501.         this.dialogElement("modeDeck").setAttribute("selectedIndex", "1");
  502.         // Hide the default handler item too, in case the user picks a 
  503.         // custom handler at a later date which triggers the menulist to show.
  504.         this.dialogElement("defaultHandler").hidden = true;
  505.       }
  506.     },
  507.  
  508.     // getPath:
  509.     getPath: function (aFile) {
  510.       return aFile.path;
  511.     },
  512.  
  513.     // initAppAndSaveToDiskValues:
  514.     initAppAndSaveToDiskValues: function() {
  515.       var modeGroup = this.dialogElement("mode");
  516.  
  517.       // We don't let users open .exe files or random binary data directly 
  518.       // from the browser at the moment because of security concerns. 
  519.       var openWithDefaultOK = this.openWithDefaultOK();
  520.       var mimeType = this.mLauncher.MIMEInfo.MIMEType;
  521.       if (this.mLauncher.targetFile.isExecutable() || (
  522.           (mimeType == "application/octet-stream" ||
  523.            mimeType == "application/x-msdownload") && 
  524.            !openWithDefaultOK)) {
  525.         this.dialogElement("open").disabled = true;
  526.         var openHandler = this.dialogElement("openHandler");
  527.         openHandler.disabled = true;
  528.         openHandler.label = "";
  529.         modeGroup.selectedItem = this.dialogElement("save");
  530.         return;
  531.       }
  532.     
  533.       // Fill in helper app info, if there is any.
  534.       this.chosenApp = this.mLauncher.MIMEInfo.preferredApplicationHandler;
  535.       // Initialize "default application" field.
  536.       this.initDefaultApp();
  537.  
  538.       var otherHandler = this.dialogElement("otherHandler");
  539.               
  540.       // Fill application name textbox.
  541.       if (this.chosenApp && this.chosenApp.path) {
  542.         otherHandler.setAttribute("path", this.getPath(this.chosenApp));
  543.         otherHandler.label = this.chosenApp.leafName;
  544.         otherHandler.hidden = false;
  545.       }
  546.  
  547.       var useDefault = this.dialogElement("useSystemDefault");
  548.       var openHandler = this.dialogElement("openHandler");
  549.       openHandler.selectedIndex = 0;
  550.  
  551.       if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
  552.         // Open (using system default).
  553.         modeGroup.selectedItem = this.dialogElement("open");
  554.       } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
  555.         // Open with given helper app.
  556.         modeGroup.selectedItem = this.dialogElement("open");
  557.         openHandler.selectedIndex = 1;
  558.       } else {
  559.         // Save to disk.
  560.         modeGroup.selectedItem = this.dialogElement("save");
  561.       }
  562.       
  563.       // If we don't have a "default app" then disable that choice.
  564.       if (!openWithDefaultOK) {
  565.         var useDefault = this.dialogElement("defaultHandler");
  566.         var isSelected = useDefault.selected;
  567.         
  568.         // Disable that choice.
  569.         useDefault.hidden = true;
  570.         // If that's the default, then switch to "save to disk."
  571.         if (isSelected) {
  572.           openHandler.selectedIndex = 1;
  573.           modeGroup.selectedItem = this.dialogElement("save");
  574.         }
  575.       }
  576.       
  577.       // otherHandler is always disabled on Mac
  578.       otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false;
  579.       this.updateOKButton();
  580.     },
  581.  
  582.     // Returns the user-selected application
  583.     helperAppChoice: function() {
  584.       return this.chosenApp;
  585.     },
  586.     
  587.     get saveToDisk() {
  588.       return this.dialogElement("save").selected;
  589.     },
  590.     
  591.     get useOtherHandler() {
  592.       return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1;
  593.     },
  594.     
  595.     get useSystemDefault() {
  596.       return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0;
  597.     },
  598.     
  599.     toggleRememberChoice: function (aCheckbox) {
  600.         this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
  601.         this.mDialog.sizeToContent();
  602.     },
  603.     
  604.     openHandlerCommand: function () {
  605.       var openHandler = this.dialogElement("openHandler");
  606.       if (openHandler.selectedItem.id == "choose")
  607.         this.chooseApp();
  608.       else
  609.         openHandler.setAttribute("lastSelectedItemID", openHandler.selectedItem.id);
  610.     },
  611.  
  612.     updateOKButton: function() {
  613.       var ok = false;
  614.       if (this.dialogElement("save").selected) {
  615.         // This is always OK.
  616.         ok = true;
  617.       } 
  618.       else if (this.dialogElement("open").selected) {
  619.         switch (this.dialogElement("openHandler").selectedIndex) {
  620.         case 0:
  621.           // No app need be specified in this case.
  622.           ok = true;
  623.           break;
  624.         case 1:
  625.           // only enable the OK button if we have a default app to use or if 
  626.           // the user chose an app....
  627.           ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path")); 
  628.         break;
  629.         }
  630.       }
  631.  
  632.       // Enable Ok button if ok to press.
  633.       this.mDialog.document.documentElement.getButton("accept").disabled = !ok;
  634.     },
  635.     
  636.     // Returns true iff the user-specified helper app has been modified.
  637.     appChanged: function() {
  638.       return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
  639.     },
  640.  
  641.     updateMIMEInfo: function() {
  642.       var needUpdate = false;
  643.       // If current selection differs from what's in the mime info object,
  644.       // then we need to update.
  645.       if (this.saveToDisk) {
  646.         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
  647.         if (needUpdate)
  648.           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
  649.       } 
  650.       else if (this.useSystemDefault) {
  651.         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
  652.         if (needUpdate)
  653.           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
  654.       } 
  655.       else {
  656.         // For "open with", we need to check both preferred action and whether the user chose
  657.         // a new app.
  658.         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
  659.         if (needUpdate) {
  660.           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
  661.           // App may have changed - Update application and description
  662.           var app = this.helperAppChoice();
  663.           this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
  664.           this.mLauncher.MIMEInfo.applicationDescription = "";
  665.         }
  666.       }
  667.       // We will also need to update if the "always ask" flag has changed.
  668.       needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);
  669.  
  670.       // One last special case: If the input "always ask" flag was false, then we always
  671.       // update.  In that case we are displaying the helper app dialog for the first
  672.       // time for this mime type and we need to store the user's action in the mimeTypes.rdf
  673.       // data source (whether that action has changed or not; if it didn't change, then we need
  674.       // to store the "always ask" flag so the helper app dialog will or won't display
  675.       // next time, per the user's selection).
  676.       needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  677.  
  678.       // Make sure mime info has updated setting for the "always ask" flag.
  679.       this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked;
  680.  
  681.       return needUpdate;        
  682.     },
  683.     
  684.     // See if the user changed things, and if so, update the
  685.     // mimeTypes.rdf entry for this mime type.
  686.     updateHelperAppPref: function() {
  687.       var ha = new this.mDialog.HelperApps();
  688.       ha.updateTypeInfo(this.mLauncher.MIMEInfo);
  689.     },
  690.     
  691.     // onOK:
  692.     onOK: function() {
  693.       // Verify typed app path, if necessary.
  694.       if (this.useOtherHandler) {
  695.         var helperApp = this.helperAppChoice();
  696.         if (!helperApp || !helperApp.exists()) {
  697.           // Show alert and try again.        
  698.           var bundle = this.dialogElement("strings");                    
  699.           var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").path]);
  700.           var svc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
  701.           svc.alert(this.mDialog, bundle.getString("badApp.title"), msg);
  702.  
  703.           // Disable the OK button.
  704.           this.mDialog.document.documentElement.getButton("accept").disabled = true;
  705.           this.dialogElement("mode").focus();          
  706.  
  707.           // Clear chosen application.
  708.           this.chosenApp = null;
  709.  
  710.           // Leave dialog up.
  711.           return false;
  712.         }
  713.       }
  714.         
  715.       // Remove our web progress listener (a progress dialog will be
  716.       // taking over).
  717.       this.mLauncher.setWebProgressListener(null);
  718.       
  719.       // saveToDisk and launchWithApplication can return errors in 
  720.       // certain circumstances (e.g. The user clicks cancel in the
  721.       // "Save to Disk" dialog. In those cases, we don't want to
  722.       // update the helper application preferences in the RDF file.
  723.       try {
  724.         var needUpdate = this.updateMIMEInfo();
  725.         
  726.         if (this.dialogElement("save").selected) {
  727.           // If we're using a default download location, create a path
  728.           // for the file to be saved to to pass to |saveToDisk| - otherwise
  729.           // we must ask the user to pick a save name.
  730.  
  731.           this.mLauncher.saveToDisk(null, false);
  732.         }
  733.         else
  734.           this.mLauncher.launchWithApplication(null, false);
  735.  
  736.         // Update user pref for this mime type (if necessary). We do not
  737.         // store anything in the mime type preferences for the ambiguous
  738.         // type application/octet-stream. We do NOT do this for 
  739.         // application/x-msdownload since we want users to be able to 
  740.         // autodownload these to disk. 
  741.         if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream")
  742.           this.updateHelperAppPref();
  743.       } catch(e) { }
  744.  
  745.       // Unhook dialog from this object.
  746.       this.mDialog.dialog = null;
  747.  
  748.       // Close up dialog by returning true.
  749.       return true;
  750.     },
  751.  
  752.     // onCancel:
  753.     onCancel: function() {
  754.       // Remove our web progress listener.
  755.       this.mLauncher.setWebProgressListener(null);
  756.  
  757.       // Cancel app launcher.
  758.       try {
  759.         this.mLauncher.Cancel();
  760.       } catch(exception) {
  761.       }
  762.  
  763.       // Unhook dialog from this object.
  764.       this.mDialog.dialog = null;
  765.  
  766.       // Close up dialog by returning true.
  767.       return true;
  768.     },
  769.  
  770.     // dialogElement:  Convenience. 
  771.     dialogElement: function(id) {
  772.       return this.mDialog.document.getElementById(id);
  773.     },
  774.  
  775.     // chooseApp:  Open file picker and prompt user for application.
  776.     chooseApp: function() {
  777.       var nsIFilePicker = Components.interfaces.nsIFilePicker;
  778.       var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  779.       fp.init(this.mDialog,
  780.               this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
  781.               nsIFilePicker.modeOpen);
  782.  
  783.       fp.appendFilters(nsIFilePicker.filterApps);
  784.  
  785.       if (fp.show() == nsIFilePicker.returnOK && fp.file) {
  786.         // Show the "handler" menulist since we have a (user-specified) 
  787.         // application now.
  788.         this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");
  789.         
  790.         // Remember the file they chose to run.
  791.         this.chosenApp = fp.file;
  792.         // Update dialog.
  793.         var otherHandler = this.dialogElement("otherHandler");
  794.         otherHandler.removeAttribute("hidden");
  795.         otherHandler.setAttribute("path", this.getPath(this.chosenApp));
  796.         otherHandler.label = this.chosenApp.leafName;
  797.         this.dialogElement("openHandler").selectedIndex = 1;
  798.         this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler");
  799.         
  800.         this.dialogElement("mode").selectedItem = this.dialogElement("open");
  801.       }
  802.       else {
  803.         var openHandler = this.dialogElement("openHandler");
  804.         var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
  805.         if (!lastSelectedID)
  806.           lastSelectedID = "defaultHandler";
  807.         openHandler.selectedItem = this.dialogElement(lastSelectedID);
  808.       }
  809.     },
  810.  
  811.     // Turn this on to get debugging messages.
  812.     debug: false,
  813.  
  814.     // Dump text (if debug is on).
  815.     dump: function( text ) {
  816.         if ( this.debug ) {
  817.             dump( text ); 
  818.         }
  819.     },
  820.  
  821.     // dumpInfo:
  822.     doDebug: function() {
  823.         const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
  824.         // Open new progress dialog.
  825.         var progress = Components.classes[ "@mozilla.org/progressdialog;1" ]
  826.                          .createInstance( nsIProgressDialog );
  827.         // Show it.
  828.         progress.open( this.mDialog );
  829.     },
  830.  
  831.     // dumpObj:
  832.     dumpObj: function( spec ) {
  833.          var val = "<undefined>";
  834.          try {
  835.              val = eval( "this."+spec ).toString();
  836.          } catch( exception ) {
  837.          }
  838.          this.dump( spec + "=" + val + "\n" );
  839.     },
  840.  
  841.     // dumpObjectProperties
  842.     dumpObjectProperties: function( desc, obj ) {
  843.          for( prop in obj ) {
  844.              this.dump( desc + "." + prop + "=" );
  845.              var val = "<undefined>";
  846.              try {
  847.                  val = obj[ prop ];
  848.              } catch ( exception ) {
  849.              }
  850.              this.dump( val + "\n" );
  851.          }
  852.     }
  853. }
  854.  
  855. // This Component's module implementation.  All the code below is used to get this
  856. // component registered and accessible via XPCOM.
  857. var module = {
  858.     firstTime: true,
  859.  
  860.     // registerSelf: Register this component.
  861.     registerSelf: function (compMgr, fileSpec, location, type) {
  862.         if (this.firstTime) {
  863.             this.firstTime = false;
  864.             throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  865.         }
  866.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  867.  
  868.         compMgr.registerFactoryLocation( this.cid,
  869.                                          "Unknown Content Type Dialog",
  870.                                          this.contractId,
  871.                                          fileSpec,
  872.                                          location,
  873.                                          type );
  874.     },
  875.  
  876.     // getClassObject: Return this component's factory object.
  877.     getClassObject: function (compMgr, cid, iid) {
  878.         if (!cid.equals(this.cid)) {
  879.             throw Components.results.NS_ERROR_NO_INTERFACE;
  880.         }
  881.  
  882.         if (!iid.equals(Components.interfaces.nsIFactory)) {
  883.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  884.         }
  885.  
  886.         return this.factory;
  887.     },
  888.  
  889.     /* CID for this class */
  890.     cid: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
  891.  
  892.     /* Contract ID for this class */
  893.     contractId: "@mozilla.org/helperapplauncherdialog;1",
  894.  
  895.     /* factory object */
  896.     factory: {
  897.         // createInstance: Return a new nsProgressDialog object.
  898.         createInstance: function (outer, iid) {
  899.             if (outer != null)
  900.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  901.  
  902.             return (new nsUnknownContentTypeDialog()).QueryInterface(iid);
  903.         }
  904.     },
  905.  
  906.     // canUnload: n/a (returns true)
  907.     canUnload: function(compMgr) {
  908.         return true;
  909.     }
  910. };
  911.  
  912. // NSGetModule: Return the nsIModule object.
  913. function NSGetModule(compMgr, fileSpec) {
  914.     return module;
  915. }
  916.